﻿// =========================================================================================================
// FAS低代码开发平台，DataObject元数据差异比较类
// 说明：
//    比较DO的差异，生成比较信息，供SQLBuilder生成SQL使用
// 主要功能：
// 1、区分环境设置列是否一致的条件，基于此进行DO列的内容比较，包括列名、类型、长度、精度、是否主键、可空、默认值等
// 2、生成差异比较结果
// 依赖框架版本：
// net451 / netstandard2.1
// =========================================================================================================
// 变更历史纪录
// 日期			版本	    修改人	描述
// 2021.8.24    1.0		user2	初稿。
// =========================================================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using T.FAS.Runtime.Metadata.Entity.DataModel;

namespace T.FAS.Runtime.DataModel.Dao
{
    /// <summary>
    /// DO元数据差异比较类
    /// </summary>
    public class DataObjectChangeChecker
    {
        /// <summary>
        /// 标识变更内容的Layer
        /// </summary>
        private const int defaultLayer = 0b0000000000;

        /// <summary>
        /// 根据发布环境不同生成不同的比较器实例
        /// </summary>
        /// <param name="checkEnvironment">发布环境</param>
        /// <returns>比较器实例</returns>
        public static IEqualityComparer<DataColumn> GetEqualityComparer(CheckEnvironment checkEnvironment)
        {
            IEqualityComparer<DataColumn> equalityComparer;
            if (checkEnvironment == CheckEnvironment.Design)
                equalityComparer = new DataColumnDesignComparer();
            else
                equalityComparer = new DataColumnReleaseComparer();
            return equalityComparer;
        }

        /// <summary>
        /// 比较方法
        /// </summary>
        /// <param name="oldObject">旧DO实体</param>
        /// <param name="newObject">新DO实体</param>
        /// <param name="checkEnvironment"></param>
        /// <returns>DO变更比较结果类</returns>
        public static DataObjectCompareResult Compare(DataObject oldObject, DataObject newObject, CheckEnvironment checkEnvironment = CheckEnvironment.Design)
        {
            DataObjectCompareResult result = new DataObjectCompareResult();
            DataColumnCompareResult compareReulst = CompareColumn(oldObject, newObject, checkEnvironment);
            result.DataColumnCompareResult = compareReulst;
            result.ParalanguageCompareResult = CompareParalanguage(oldObject, newObject);
            return result;
        }

        /// <summary>
        /// 整体比较DO列信息
        /// </summary>
        /// <param name="oldObject">旧DO实体</param>
        /// <param name="newObject">新DO实体</param>
        /// <returns>DO变更比较结果类</returns>
        internal static DataColumnCompareResult CompareColumn(DataObject oldObject, DataObject newObject, CheckEnvironment checkEnvironment)
        {
            //获取比较器
            var comparer = GetEqualityComparer(checkEnvironment);
            //比较结果
            DataColumnCompareResult CompareResult = new DataColumnCompareResult();
            //需要drop的列
            var needDropColumns = oldObject.Columns.Except(newObject.Columns, comparer).ToList();   //一定要指定Comparer
            //需要add的列
            var needAddColumns = newObject.Columns.Except(oldObject.Columns, comparer).ToList();   //一定要指定Comparer
            //存在差异的列
            var needModifyColumn = new List<ModifyColumnInfo>();
            //待比较详情的列
            var needCompareDetailColumns = oldObject.Columns
                .Except(needDropColumns, comparer).ToList();      //一定要指定Comparer
            //待比较详情的列
            var needCompareDetailColumnIdentifiers =
                needCompareDetailColumns
                .Select(col => checkEnvironment == CheckEnvironment.Design ? col.ID : col.ColumnName)
                .ToList();

            foreach (var identifier in needCompareDetailColumnIdentifiers)
            {
                //设计时通过比较DO.Column.ID，来判断是否为同一个DataColumn，运行时通过比较 ColumName是否相同来判断
                var oldCol = oldObject.Columns.Find(col => checkEnvironment == CheckEnvironment.Design
                                                            ? col.ID == identifier
                                                            : string.Equals(col.ColumnName, identifier, StringComparison.OrdinalIgnoreCase));
                var newCol = newObject.Columns.Find(col => checkEnvironment == CheckEnvironment.Design 
                                                            ? col.ID == identifier
                                                            : string.Equals(col.ColumnName, identifier, StringComparison.OrdinalIgnoreCase));
                int columnCompareResult = CompareColumnDetails(oldCol, newCol);
                if (columnCompareResult != defaultLayer)
                {
                    //如果为发布模式，且只有Guid不相等时，不需要生成Alter Column语句
                    if (checkEnvironment == CheckEnvironment.Release && (columnCompareResult == (int)ChangeType.ID))
                        continue;

                    //默认值有改动，暂不处理
                    if ((columnCompareResult & (int)ChangeType.DefaultValue) == (int)ChangeType.DefaultValue)
                        ;

                    //如果其余列信息有改动，以新列为标准进行Alter
                    needModifyColumn.Add(new ModifyColumnInfo
                    {
                        ChangeTypeLayer = columnCompareResult,
                        NewColumn = newCol,
                    });
                }
            }
            CompareResult.NeedAddColumns = needAddColumns.OrderBy(o => o.ColumnOrder).ToList();
            CompareResult.NeedDropColumns = needDropColumns.OrderBy(o => o.ColumnOrder).ToList();
            CompareResult.NeedModifyColumns = needModifyColumn.OrderBy(o => o.NewColumn.ColumnOrder).ToList();
            return CompareResult;
        }

        /// <summary>
        /// 比较两个列信息的异同
        /// </summary>
        /// <param name="old">旧列</param>
        /// <param name="new">新列</param>
        /// <returns></returns>
        private static int CompareColumnDetails(DataColumn old, DataColumn @new)
        {
            int layer = defaultLayer;
            if (old.ID != @new.ID)
                layer |= (int)ChangeType.ID;
            if (!string.Equals(old.ColumnName, @new.ColumnName, StringComparison.OrdinalIgnoreCase))  //比较列名时忽略大小写
                layer |= (int)ChangeType.ColumnName;
            if (old.DataType != @new.DataType)
                layer |= (int)ChangeType.DataType;
            if (old.Length != @new.Length)
                layer |= (int)ChangeType.Length;
            if (old.Precision != @new.Precision)
                layer |= (int)ChangeType.Precision;
            if (old.IsPkColumn != @new.IsPkColumn)
                layer |= (int)ChangeType.IsPkColumn;
            if (old.IsNullable != @new.IsNullable)
                layer |= (int)ChangeType.IsNullable;
            if (old.DefaultValue != @new.DefaultValue)
                layer |= (int)ChangeType.DefaultValue;
            if (old.IsInternational != @new.IsInternational)
                layer |= (int)ChangeType.IsInternational;
            if (@new.MaskingRule.IsEnable && !old.MaskingRule.IsEnable)
                layer |= (int)ChangeType.MaskSetting;
            if (old.EnableUnicode != @new.EnableUnicode)
                layer |= (int)ChangeType.Unicode;
            if (old.RelatedDataObjectPrimaryKey != null
               && @new.RelatedDataObjectPrimaryKey != null
               && !string.IsNullOrEmpty(old.RelatedDataObjectPrimaryKey.DataType)
               && !string.IsNullOrEmpty(@new.RelatedDataObjectPrimaryKey.DataType)
               && !string.Equals(old.RelatedDataObjectPrimaryKey.DataType, @new.RelatedDataObjectPrimaryKey.DataType)
               )
                layer |= (int)ChangeType.ReleatedPK;
            return layer;
        }

        /// <summary>
        /// 比较辅助语言变更信息
        /// </summary>
        /// <param name="oldObject"></param>
        /// <param name="newObject"></param>
        /// <returns></returns>
        private static DataObjectParalanguageCompareResult CompareParalanguage(DataObject oldObject, DataObject newObject)
        {
            DataObjectParalanguageCompareResult result = new DataObjectParalanguageCompareResult();
            List<KeyValuePair<string, string>> oldList = oldObject.ParalanguageTableI18NFieldSuffixMapping?.ToList();
            List<KeyValuePair<string, string>> newList = newObject.ParalanguageTableI18NFieldSuffixMapping?.ToList();
            Func<KeyValuePair<string, string>, KeyValuePair<string, string>, bool> identifierEqualFunc = (x, y) => x.Key == y.Key;
            Func<KeyValuePair<string, string>, KeyValuePair<string, string>, bool> bodyEqualFunc = (x, y) => x.Value == y.Value;
            result.IsChanged = CompareTool.Compare(oldList,
                                                    newList,
                                                    identifierEqualFunc,
                                                    bodyEqualFunc,
                                                    out List<KeyValuePair<string, string>> needAdd,
                                                    out List<KeyValuePair<string, string>> needDelete,
                                                    out List<KeyValuePair<string, string>> needUpdateOld,
                                                    out List<Tuple<KeyValuePair<string, string>, KeyValuePair<string, string>>> needUpdate);
            result.ChangedParalanguage = new List<string>();
            result.ChangedParalanguage.AddRange(needAdd.Select(q => q.Key));
            result.ChangedParalanguage.AddRange(needDelete.Select(q => q.Key));
            result.ChangedParalanguage.AddRange(needUpdateOld.Select(q => q.Key));
            return result;
        }
    }

    /// <summary>
    /// 设计时DO.Column比较类
    /// </summary>
    class DataColumnDesignComparer : IEqualityComparer<DataColumn>
    {
        public bool Equals(DataColumn x, DataColumn y)
        {
            return x.ID == y.ID;
        }

        public int GetHashCode(DataColumn obj)
        {
            return obj.ID.GetHashCode();
        }
    }

    /// <summary>
    /// 发布时DO.Column比较类
    /// </summary>
    class DataColumnReleaseComparer : IEqualityComparer<DataColumn>
    {
        public bool Equals(DataColumn x, DataColumn y)
        {
            //发布时比较列名 忽略大小写
            return string.Equals(x.ColumnName, y.ColumnName, StringComparison.OrdinalIgnoreCase);
        }

        public int GetHashCode(DataColumn obj)
        {
            return obj.ColumnName.ToLower().GetHashCode();
        }
    }

    /// <summary>
    /// 列变更类型，包括列名、类型、长度、精度、是否主键、可空、默认值、国际化等
    /// </summary>
    public enum ChangeType
    {
        ID                  = 0b000000000001,
        ColumnName          = 0b000000000010,
        DataType            = 0b000000000100,
        Length              = 0b000000001000,
        Precision           = 0b000000010000,
        IsPkColumn          = 0b000000100000,
        IsNullable          = 0b000001000000,
        DefaultValue        = 0b000010000000,
        IsInternational     = 0b000100000000,
        MaskSetting         = 0b001000000000,
        Unicode             = 0b010000000000,
        ReleatedPK          = 0b100000000000,
    }

    /// <summary>
    /// 校验环境
    /// </summary>
    public enum CheckEnvironment
    {
        Design  = 0,
        Release = 1,
    }
}
